package org.hsc.proxy;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.HashMap;
import java.util.concurrent.BlockingQueue;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * A simple Http proxy router (singleton). The router, routes Http Requests to a pool of connected hosts.
 * 
 * @author Moty Michaely
 */
public class HttpProxyRouter {
	private static final Logger logger=LoggerFactory.getLogger(HttpProxyRouter.class);
    /**
     * Requests Consumer.
     */
    private class RequestsConsumer implements Runnable {
        public void run() {
            try{
                route(entries.take()); 
            } catch (InterruptedException ex) {
            	logger.error(ex.getMessage(),ex);
            } 
        }
        
        /**
         * Routes the specified request
         * 
         * @param request   The request to route
         */
        private void route(HandlerEntry entry) {
            // Return if there's nothing to be processed.
            if( entry  == null ){
            	logger.warn("route Error: entry is null");
                return;
            }
            HttpContext context = entry.getContext();
            HttpRequest request = context.getRequest();
            HttpProxyHostHandler hostHandler = null;
            // Create a new host handler if not exists.
            try {
            	hostHandler=hosts.get(request.getHost());
            	
                if( hostHandler == null ){
                    // Create a new host handler
                    hostHandler = new HttpProxyHostHandler( proxyServer, request.getHost(), request.getPort());
                    hosts.put(request.getHost(), hostHandler);
                    // Set server connection type
                    context.setServerConnectionType(HttpProxyServer.ConnectionType.New);
                    
                } else{
                    // Set server connection type
                    context.setServerConnectionType(HttpProxyServer.ConnectionType.Existing);
                }
                // Add the request to the host handler queue
                hostHandler.queue(entry);
                // That's it!!!
            } catch (Exception e) {
            	logger.error("Router Error: " + e.getMessage(),e);
            }
        }
    }
    
    /**
     * Holds the singleton instance. Use volatile to fix broken double check locking.
     */
    private static volatile HttpProxyRouter instance=null;
    
    /**
     * Holds an hashtable of host handlers. 
     * The key is a string identifying the host to route the requests to.
     */
    private volatile HashMap<String, HttpProxyHostHandler> hosts;
    
    /** Holds a queue of http contexts to be routed.*/
    private volatile BlockingQueue<HandlerEntry> entries;
    
    /**Holds consumer thread.*/
    private Thread consumer;
    
    /**Holds the proxy server this client is being handled by. */
    private HttpProxyServer proxyServer=null;   
    
    /** Protected constructor*/
    protected HttpProxyRouter() {
        this.hosts = new HashMap<String, HttpProxyHostHandler>();
        this.entries = new LinkedBlockingQueue<HandlerEntry>();
    }
    
    /** Returns the singleton instance. */
    public static HttpProxyRouter getInstance() {
        if( instance == null ) {
            synchronized( HttpProxyRouter.class ) {
                if( instance == null )
                    instance = new HttpProxyRouter();
            }
        }
        return instance;
    }
    
    /** Queues the specified http context for routing. */
    public synchronized void route(HttpContext context, HttpResponseCallback responseCallback) {
        try {
            this.entries.put(new HandlerEntry(context, responseCallback));
        } catch (InterruptedException ex) {
        	logger.error(ex.getMessage(),ex);
        }
    }
    
    /** Starts routing requests. */
    public void start(HttpProxyServer proxyServer) {
        this.proxyServer = proxyServer;
        this.consumer = new Thread(new RequestsConsumer(), "Router Requests Consumer");
        this.consumer.start();
    }
    
    /**
     * Stops routing requests.
     */
    public void stop() {
        try {
            if( this.consumer != null )
                this.consumer.interrupt();
        }catch (SecurityException ex) {
        	logger.error(ex.getMessage(),ex);
        }
    }
    
    /**
     * Gets the HttpProxyServer associated with this router
     */
    public HttpProxyServer getProxyServer() {
        return this.proxyServer;
    }

}
